﻿using Ping9719.DeviceCommunication.Clients.PLC.Models;
using Ping9719.DeviceCommunication.Common.Helpers;
using Ping9719.DeviceCommunication.Enums;
using Ping9719.DeviceCommunication.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Ping9719.DeviceCommunication.Clients.PLC
{
    /// <summary>
    /// 欧姆龙客户端（Cip协议）
    /// https://www.cnblogs.com/funiyi816/p/17107861.html
    /// </summary>
    public class OmronCipClient : SocketBase, IIoTBase
    {
        private EndianFormat endianFormat;
        private Encoding encoding = Encoding.UTF8;

        /// <summary>
        /// 注册会话ID
        /// </summary>
        private byte[] Registercmd = new byte[]
        {
            0x6F,0x00,//请求命令
　　        0x04,0x00,//长度
　　        0x00,0x00,0x00,0x00,//会话句柄
　　        0x00,0x00,0x00,0x00,//状态默认0
　　        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//发送方描述默认0
　　        0x00,0x00,0x00,0x00,//选项默认0
          　0x01,0x00,//协议版本
            0x00,0x00,//选项标记
        };

        private byte[] Header = new byte[]
        {
            //24byte
            0x6F,0x00,//命令 2byte
　　        0x28,0x00,//长度 2byte（总长度-Header的长度）=40 
　　        0x6B,0x01,0x01,0x00,//会话句柄 4byte
　　        0x00,0x00,0x00,0x00,//状态默认0 4byte
　　        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//发送方描述默认0 8byte
　　        0x00,0x00,0x00,0x00,//选项默认0 4byte
        };

        private byte[] CommandSpecificData = new byte[]
        {
            //16
            0x00,0x00,0x00,0x00,//接口句柄 CIP默认为0x00000000 4byte
　　        0x01,0x00,//超时默认0x0001 4byte
　　        0x02,0x00,//项数默认0x0002 4byte
　　        0x00,0x00,//空地址项默认0x0000 2byte
　　        0x00,0x00,//长度默认0x0000 2byte
　　        0xb2,0x00,//未连接数据项默认为 0x00b2
　　        0x18,0x00,//后面数据包的长度 24个字节(总长度-Header的长度-CommandSpecificData的长度)
        };

        /// <summary>
        /// 读数据服务请求报文（三部分组成 Header 24个字节 、CommandSpecificData 16个字节、以及CIP消息（由读取的标签生成））
        /// </summary>
        private byte[] CipMessage = new byte[]
        {
            //24 
            0x52,0x02,//服务默认0x52  请求路径大小 默认2
            0x22,0x06,0x24,0x01,//请求路径 默认0x01240622 4byte
　　        0x0A,0xF0,//超时默认0xF00A 4byte
　　        0x0A,0x00,//Cip指令长度  服务标识到服务命令指定数据的长度
　　        0x4C,//服务标识固定为0x4C 1byte  
　　        0x03,// 节点长度 2byte  规律为 (标签名的长度+1/2)+1
　　        0x91,//扩展符号 默认为 0x91
　　        0x04,//标签名的长度
　　        //0x54,0x41,0x47,0x31,//标签名 ：TAG1转换成ASCII字节 当标签名的长度为奇数时，需要在末尾补0  比如TAG转换成ASCII为0x54,0x41,0x47，需要在末尾补0 变成 0x54,0x41,0x47，0
            //0x01,0x00,//服务命令指定数据　默认为0x0001　
　          //0x01,0x00,0x01,0x00//最后一位是PLC的槽号
        };

        private byte[] GetReadComm(string address)
        {
            var aa = encoding.GetBytes(address).ToList();

            var mess = CipMessage.ToList();
            mess[13] = Convert.ToByte(aa.Count());

            //奇数
            if (aa.Count % 2 == 1)
                aa.Add(0);

            mess.AddRange(aa);
            mess.AddRange(new byte[] { 0x01, 0x00, 0x01, 0x00, 0x01, 0x00 });
            var ciplen = BitConverter.GetBytes(Convert.ToInt16(6 + aa.Count));
            mess[8] = ciplen[0];
            mess[9] = ciplen[1];
            mess[11] = Convert.ToByte((mess[13] + 1) / 2 + 1);

            var spelen = BitConverter.GetBytes(Convert.ToInt16(mess.Count));
            CommandSpecificData[14] = spelen[0];
            CommandSpecificData[15] = spelen[1];

            var healen = BitConverter.GetBytes(Convert.ToInt16(mess.Count + CommandSpecificData.Length));
            Header[2] = spelen[0];
            Header[3] = spelen[1];
            Header[4] = SessId[0];
            Header[5] = SessId[1];
            Header[6] = SessId[2];
            Header[7] = SessId[3];

            return Header.Concat(CommandSpecificData).Concat(mess).ToArray();
        }

        /// <summary>
        /// 版本
        /// </summary>
        public string Version => "OmronFins";

        /// <summary>
        /// 是否是连接的
        /// </summary>
        public override bool IsConnected => socket?.Connected ?? false;

        /// <summary>
        /// 会话id
        /// </summary>
        private byte[] SessId = new byte[4] { 0, 0, 0, 0 };

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        /// <param name="timeout"></param>
        /// <param name="endianFormat"></param>
        public OmronCipClient(string ip, int port = 44818, int timeout = 1500, EndianFormat endianFormat = EndianFormat.CDAB)
        {
            SetIpEndPoint(ip, port);
            this.timeout = timeout;
            this.endianFormat = endianFormat;
        }

        /// <summary>
        /// 打开连接（如果已经是连接状态会先关闭再打开）
        /// </summary>
        /// <returns></returns>
        protected override Result Connect()
        {
            var result = new Result();
            SafeClose();
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                //超时时间设置
                socket.ReceiveTimeout = timeout;
                socket.SendTimeout = timeout;

                //socket.Connect(ipEndPoint);
                IAsyncResult connectResult = socket.BeginConnect(ipEndPoint, null, null);
                //阻塞当前线程           
                if (!connectResult.AsyncWaitHandle.WaitOne(timeout))
                    throw new TimeoutException("连接超时");
                socket.EndConnect(connectResult);

                result.Requst = string.Join(" ", Registercmd.Select(t => t.ToString("X2")));
                socket.Send(Registercmd);

                var socketReadResul = SocketRead(28);
                if (!socketReadResul.IsSucceed)
                    return socketReadResul;
                if (!socketReadResul.Value.Skip(8).Take(4).All(o => o == 0))
                {
                    socketReadResul.IsSucceed = false;
                    socketReadResul.Err = "失败";
                    return socketReadResul;
                }
                SessId = socketReadResul.Value.Skip(4).Take(4).ToArray();

                //byte[] buffer = new byte[4];
                //buffer[0] = head[7];
                //buffer[1] = head[6];
                //buffer[2] = head[5];
                //buffer[3] = head[4];
                //var length = BitConverter.ToInt32(buffer, 0);

                //socketReadResul = SocketRead(length);
                //if (!socketReadResul.IsSucceed)
                //    return socketReadResul;
                //var content = socketReadResul.Value;

                //var headContent = head.Concat(content).ToArray();
                //result.Response = string.Join(" ", headContent.Select(t => t.ToString("X2")));
                //// 服务器节点编号
                //if (headContent.Length >= 24) DA1 = headContent[23];
                //else DA1 = Convert.ToByte(ipEndPoint.Address.ToString().Substring(ipEndPoint.Address.ToString().LastIndexOf(".") + 1));
            }
            catch (Exception ex)
            {
                SafeClose();
                result.IsSucceed = false;
                result.Err = ex.Message;
                result.ErrCode = 408;
                result.Exception = ex;
            }
            return result.EndTime();
        }

        /// <summary>
        /// 发送报文，并获取响应报文（建议使用SendPackageReliable，如果异常会自动重试一次）
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        public override Result<byte[]> SendPackageSingle(byte[] command)
        {
            //从发送命令到读取响应为最小单元，避免多线程执行串数据（可线程安全执行）
            lock (this)
            {
                Result<byte[]> result = new Result<byte[]>();
                try
                {
                    socket.Send(command);
                    var socketReadResul = SocketRead(8);
                    if (!socketReadResul.IsSucceed)
                        return socketReadResul;
                    var head = socketReadResul.Value;

                    byte[] buffer = new byte[4];
                    buffer[0] = head[7];
                    buffer[1] = head[6];
                    buffer[2] = head[5];
                    buffer[3] = head[4];
                    //4-7是Length字段 表示其后所有字段的总长度
                    var contentLength = BitConverter.ToInt32(buffer, 0);
                    socketReadResul = SocketRead(contentLength);
                    if (!socketReadResul.IsSucceed)
                        return socketReadResul;
                    var dataPackage = socketReadResul.Value;

                    result.Value = head.Concat(dataPackage).ToArray();
                    return result.EndTime();
                }
                catch (Exception ex)
                {
                    result.IsSucceed = false;
                    result.Err = ex.Message;
                    result.AddErr2List();
                    return result.EndTime();
                }
            }
        }

        #region IIoTBase
        public Result<T> Read<T>(string address)
        {
            address = address.Trim();
            var result = new Result<T>();
            if (string.IsNullOrEmpty(address))
            {
                result.IsSucceed = false;
                result.Err = "地址不能为空";
                return result;
            }

            var data = GetReadComm(address);
            var read = SendPackageSingle(data);
            if (!read.IsSucceed)
                return new Result<T>(read);

            var da = read.Value.Skip(84).ToList();
            if (da.Count() < 6)
            {
                result.IsSucceed = false;
                result.Err = "效验失败";
                return result;
            }
            if (da[0] != 0 || da[1] != 0)
            {
                result.IsSucceed = false;
                result.Err = "失败";
                return result;
            }
            //数据

            //类型 (0x00C3(195) 为整型，0x00CA(202)为实型， 0x00C1(193)为布尔型,long 型为0x00C4，BYTE为0x00C2 )
            //C1-BOOL C2-SINT C3-Short C4-Int  C7-UShort C8-UInt  CA-Float CB -Double  D0-String
            var type = da[2];
            if (true)
            {

            }

            //var tType = typeof(T);
            //if (tType == typeof(bool))
            //{
            //    var readResut = ReadBoolean(address);
            //    return new Result<T>(readResut, (T)(object)readResut.Value);
            //}
            //else if (tType == typeof(byte))
            //{
            //    var readResut = ReadByte(address);
            //    return new Result<T>(readResut, (T)(object)readResut.Value);
            //}
            //else if (tType == typeof(float))
            //{
            //    var readResut = ReadFloat(address);
            //    return new Result<T>(readResut, (T)(object)readResut.Value);
            //}
            //else if (tType == typeof(double))
            //{
            //    var readResut = ReadDouble(address);
            //    return new Result<T>(readResut, (T)(object)readResut.Value);
            //}
            //else if (tType == typeof(short))
            //{
            //    var readResut = ReadInt16(address);
            //    return new Result<T>(readResut, (T)(object)readResut.Value);
            //}
            //else if (tType == typeof(int))
            //{
            //    var readResut = ReadInt32(address);
            //    return new Result<T>(readResut, (T)(object)readResut.Value);
            //}
            //else if (tType == typeof(long))
            //{
            //    var readResut = ReadInt64(address);
            //    return new Result<T>(readResut, (T)(object)readResut.Value);
            //}
            //else if (tType == typeof(ushort))
            //{
            //    var readResut = ReadUInt16(address);
            //    return new Result<T>(readResut, (T)(object)readResut.Value);
            //}
            //else if (tType == typeof(uint))
            //{
            //    var readResut = ReadUInt32(address);
            //    return new Result<T>(readResut, (T)(object)readResut.Value);
            //}
            //else if (tType == typeof(ulong))
            //{
            //    var readResut = ReadUInt64(address);
            //    return new Result<T>(readResut, (T)(object)readResut.Value);
            //}
            //else if (tType == typeof(string))
            //{
            //    var readResut = ReadString(address);
            //    return new Result<T>(readResut, (T)(object)readResut.Value);
            //}
            //else
            //{
            //    throw new NotImplementedException("暂不支持的类型");
            //}

            return result;
        }

        public Result<string> ReadString(string address, int length, Encoding encoding)
        {
            throw new NotImplementedException();
        }

        public Result<IEnumerable<T>> Read<T>(string address, int number)
        {
            throw new NotImplementedException("CIP协议暂不支持多个读取");
        }

        public Result Write<T>(string address, T value)
        {
            if (value is bool boolv)
            {
                return Write(address, boolv);
            }
            else if (value is byte bytev)
            {
                return Write(address, bytev);
            }
            else if (value is sbyte sbytev)
            {
                return Write(address, sbytev);
            }
            else if (value is float floatv)
            {
                return Write(address, floatv);
            }
            else if (value is double doublev)
            {
                return Write(address, doublev);
            }
            else if (value is short Int16v)
            {
                return Write(address, Int16v);
            }
            else if (value is int Int32v)
            {
                return Write(address, Int32v);
            }
            else if (value is long Int64v)
            {
                return Write(address, Int64v);
            }
            else if (value is ushort UInt16v)
            {
                return Write(address, UInt16v);
            }
            else if (value is uint UInt32v)
            {
                return Write(address, UInt32v);
            }
            else if (value is ulong UInt64v)
            {
                return Write(address, UInt64v);
            }
            else if (value is string Stringv)
            {
                return Write(address, Stringv);
            }
            else
            {
                throw new NotImplementedException("暂不支持的类型");
            }
        }

        public Result WriteString(string address, string value, int length, Encoding encoding)
        {
            throw new NotImplementedException();
        }

        public Result Write<T>(string address, params T[] value)
        {
            throw new NotImplementedException("CIP协议暂不支持多个写入");
        }

        #endregion
    }
}
